import * as fs from "fs";
import * as path from "path";
import * as morph from "ts-morph";

const allCodeFileList: string[] = [];
const allCodeFileImportFileListDict: { [key: string]: string[] } = {};

const allModuleFileList: string[] = [];
const allExpandFileList: string[] = [];

const originModule2ExpandFileListDict: { [key: string]: string[] } = {};
const originExpand2ModuleFileListDict: { [key: string]: string[] } = {};

const module2ExpandFileListDict: { [key: string]: string[] } = {};
const expand2ModuleFileListDict: { [key: string]: string[] } = {};

const allCollectedFileImportFileListDict: { [key: string]: string[] } = {};

function CheckFileNeedPreLoad(inputPath: string) {
    const project = new morph.Project({ compilerOptions: { target: morph.ts.ScriptTarget.ES2016 } });
    project.addSourceFileAtPathIfExists(inputPath);

    const morphSourceFile = project.getSourceFileOrThrow(inputPath);

    const baseFileName = path.basename(inputPath);
    // const validFileNameList = [
    //     "MessageHelper.ts",
    //     "ActorLocationSenderComponent.ts",
    //     "ActorLocationSenderComponentSystem.ts",
    // ];
    // if (validFileNameList.indexOf(baseFileName) == -1) {
    //     return;
    // }

    allCodeFileList.push(inputPath);

    const importDecls = morphSourceFile.getImportDeclarations();
    importDecls?.forEach(importDecl => {
        const moduleImportPath = importDecl.getModuleSpecifierValue();
        const moduleFilePath = path.join(path.dirname(inputPath), moduleImportPath);
        const moduleFile = moduleFilePath + ".ts";
        if (!allCodeFileImportFileListDict[inputPath]) {
            allCodeFileImportFileListDict[inputPath] = [];
        }
        allCodeFileImportFileListDict[inputPath].push(moduleFile);
    });

    // 收集所有 declare module 扩展方法
    const moduleDecls = morphSourceFile.getModules();
    moduleDecls?.forEach(moduleDecl => {
        const moduleStructrue = moduleDecl.getStructure();
        if (!moduleStructrue.hasDeclareKeyword) {
            return;
        }
        if (moduleStructrue.declarationKind != "module") {
            return;
        }
        const moduleImportPath = moduleStructrue.name.replace(/\"/g, "");
        const moduleFilePath = path.join(path.dirname(inputPath), moduleImportPath);
        const moduleFile = moduleFilePath + ".ts";
        if (allModuleFileList.indexOf(moduleFile) == -1) {
            allModuleFileList.push(moduleFile);
        }
        if (allExpandFileList.indexOf(inputPath) == -1) {
            allExpandFileList.push(inputPath);
        }

        if (!originModule2ExpandFileListDict[moduleFile]) {
            originModule2ExpandFileListDict[moduleFile] = [];
        }
        if (originModule2ExpandFileListDict[moduleFile].indexOf(inputPath) == -1) {
            originModule2ExpandFileListDict[moduleFile].push(inputPath);
        } else {
            // console.log(`${inputPath} 已存在`);
        }

        if (!module2ExpandFileListDict[moduleFile]) {
            module2ExpandFileListDict[moduleFile] = [];
        }
        if (module2ExpandFileListDict[moduleFile].indexOf(inputPath) == -1) {
            module2ExpandFileListDict[moduleFile].push(inputPath);
        } else {
            // console.log(`${inputPath} 已存在`);
        }

        if (!originExpand2ModuleFileListDict[inputPath]) {
            originExpand2ModuleFileListDict[inputPath] = [];
        }
        if (originExpand2ModuleFileListDict[inputPath].indexOf(moduleFile) == -1) {
            originExpand2ModuleFileListDict[inputPath].push(moduleFile);
        } else {
            // console.log(`${moduleFile} 已存在`);
        }

        if (!expand2ModuleFileListDict[inputPath]) {
            expand2ModuleFileListDict[inputPath] = [];
        }
        if (expand2ModuleFileListDict[inputPath].indexOf(moduleFile) == -1) {
            expand2ModuleFileListDict[inputPath].push(moduleFile);
        } else {
            // console.log(`${moduleFile} 已存在`);
        }
    });

}

async function BuildValidCheck(srcFilePath: string) {
    const ext = path.extname(srcFilePath);
    if (ext != ".ts") {
        return;
    }
    CheckFileNeedPreLoad(srcFilePath);
}

async function TraverseDirectory(needFilterFolderOrFileList: string[], dirPath: string) {
    const files = fs.readdirSync(dirPath);
    files.forEach(async (file) => {
        if (needFilterFolderOrFileList.indexOf(file) !== -1) {
            return;
        }
        const filePath = path.join(dirPath, file);
        Build(needFilterFolderOrFileList, filePath);
    });
}

function Build(needFilterFolderOrFileList: string[], filePath: string) {
    const stats = fs.statSync(filePath);
    if (stats.isDirectory()) {
        TraverseDirectory(needFilterFolderOrFileList, filePath);
    } else {
        BuildValidCheck(filePath);
    }
}

function CheckOthersFileNeedPreLoad(needFilterFolderOrFileList: string[], inputPath: string) {
    let needPreLoad = false;
    const validFileList = GetValidFileList(inputPath);
    if (validFileList.length != 0) {
        for (let i = 0; i < validFileList.length; i++) {
            const needFile = validFileList[i];
            if (needFilterFolderOrFileList.indexOf(needFile) != -1) {
                needPreLoad = true;
                break;
            }
        }
    }
    return needPreLoad;
}

function BuildOthers(needFilterFolderOrFileList: string[]) {
    let otherFilePathList: string[] = [];
    let otherFullPathList: string[] = [];
    allCodeFileList.forEach(async (filePath) => {
        if (needFilterFolderOrFileList.indexOf(filePath) != -1) {
            return;
        }
        const needPreLoad = CheckOthersFileNeedPreLoad(needFilterFolderOrFileList, filePath);
        if (needPreLoad) {
            const relatFilePath = filePath.replace(realPath, "");
            if (otherFilePathList.indexOf(relatFilePath) == -1) {
                otherFilePathList.push(relatFilePath);
            }
            otherFullPathList.push(filePath);
        }
    });
    return [otherFilePathList, otherFullPathList];
}

function IsValidModuleFile(file: string, collectedFileList: string[], needFileList: string[]) {
    // 先判断是否为基础类
    const index = allModuleFileList.indexOf(file);
    if (index == -1) {
        return true;
    }
    if (needFileList.length == 1) {
        if (collectedFileList.indexOf(file) == -1) {
            return false;
        }
    } else {
        const fileList = originModule2ExpandFileListDict[file];
        for (let i = 0; i < fileList.length; i++) {
            const expandFile = fileList[i];
            if (collectedFileList.indexOf(expandFile) == -1) {
                return false;
            }
        }
    }
    return true;
}

function GetValidFileList(file: string) {
    const validFileList: string[] = [];
    const fileList = allCodeFileImportFileListDict[file];
    if (fileList) {
        for (let i = 0; i < fileList.length; i++) {
            const needFile = fileList[i];
            if (allModuleFileList.indexOf(needFile) == -1 && allExpandFileList.indexOf(needFile) == -1) {
                continue;
            }
            validFileList.push(needFile);
        }
    }
    return validFileList;
}

function FileIsCollected(validFile: string, collectedFileList: string[]) {
    if (collectedFileList.indexOf(validFile) == -1) {
        return false;
    }
    return true;
}

function FileListIsCollected(fileList: string[], collectedFileList: string[]) {
    for (let i = 0; i < fileList.length; i++) {
        let file = fileList[i];
        if (!FileIsCollected(file, collectedFileList)) {
            return false;
        }
    }
    return true;
}

function ExpandFileIsCollected(validFile: string, collectedFileList: string[]) {
    const fileList = GetValidFileList(validFile);
    const mFileList = expand2ModuleFileListDict[validFile];
    for (let i = 0; i < fileList.length; i++) {
        const needFile = fileList[i];
        if (mFileList.indexOf(needFile) == -1) {
            const mIndex = allModuleFileList.indexOf(needFile);
            if (mIndex == -1) {
                const tempFileList = expand2ModuleFileListDict[needFile];
                if (!FileIsCollected(needFile, collectedFileList) || !FileListIsCollected(tempFileList, collectedFileList)) {
                    return false;
                }
            } else {
                const tempFileList = module2ExpandFileListDict[needFile];
                if (!FileIsCollected(needFile, collectedFileList) || !FileListIsCollected(tempFileList, collectedFileList)) {
                    return false;
                }
            }
        } else {
            if (!FileIsCollected(needFile, collectedFileList)) {
                return false;
            }
        }
    }
    return true;
}

function GetSortedFilePathList(rootPath: string) {
    const pathList: string[] = [];
    const fullPathList: string[] = [];
    const collectedFileList: string[] = [];
    const sortedModuleFileList: string[] = [];
    const allFileList: string[] = [];
    allModuleFileList.forEach((file) => {
        if (allFileList.indexOf(file) == -1) {
            allFileList.push(file);
        }
    });
    allExpandFileList.forEach((file) => {
        if (allFileList.indexOf(file) == -1) {
            allFileList.push(file);
        }
    });
    let index = 0;
    while (allModuleFileList.length != collectedFileList.length) {
        let moduleFile = allModuleFileList[index];

        if (collectedFileList.indexOf(moduleFile) == -1) {
            let isSupported = true;
            const validFileList = GetValidFileList(moduleFile);
            if (validFileList.length != 0) {
                for (let i = 0; i < validFileList.length; i++) {
                    const validFile = validFileList[i];
                    if (FileIsCollected(validFile, collectedFileList)) {
                        continue;
                    }
                    isSupported = false;
                }
            }
            if (isSupported) {
                if (collectedFileList.indexOf(moduleFile) == -1) {
                    collectedFileList.push(moduleFile);
                    sortedModuleFileList.push(moduleFile);
                    // console.log(moduleFile);
                }
            }
        }

        index++;
        if (index >= allModuleFileList.length) {
            index = 0;
        }
    }
    index = 0;
    while (allFileList.length != collectedFileList.length) {
        let moduleFile = sortedModuleFileList[index];

        const expandFileList = originModule2ExpandFileListDict[moduleFile];
        if (expandFileList.length != 0) {
            for (let i = 0; i < expandFileList.length; i++) {
                const expandFile = expandFileList[i];
                if (collectedFileList.indexOf(expandFile) == -1) {
                    if (!ExpandFileIsCollected(expandFile, collectedFileList)) {
                        continue;
                    }
                    if (collectedFileList.indexOf(expandFile) == -1) {
                        collectedFileList.push(expandFile);
                        // console.log(expandFile);
                    }
                }
            }
        }

        index++;
        if (index >= sortedModuleFileList.length) {
            index = 0;
        }
    }
    collectedFileList.forEach((tempFilePath) => {
        const relatFile = tempFilePath.replace(rootPath, "");
        if (pathList.indexOf(relatFile) == -1) {
            pathList.push(relatFile);
        }
        fullPathList.push(tempFilePath);
    });
    return [pathList, fullPathList];
}

function BuildPreLoader(inputPath: string, filePathList: string[]) {
    const project = new morph.Project({ compilerOptions: { target: morph.ts.ScriptTarget.ES2016 } });
    project.addSourceFileAtPathIfExists(inputPath);
    // 获取包含当前光标位置的节点。
    const morphSourceFile = project.getSourceFileOrThrow(inputPath);
    const classDecl = morphSourceFile.getClass("CodePreLoader");
    const methodDecl = classDecl.getStaticMethod("GetFilePathList");
    methodDecl.removeBody();
    methodDecl.addStatements(`let list: string[] = []; `);
    filePathList.forEach(filePath => {
        methodDecl.addStatements(`list.push("${filePath.replace(/\\/g, "/")}"); `);
    });
    methodDecl.addStatements(`return list; `);
    const newFileContent = morphSourceFile.getFullText();
    fs.writeFileSync(inputPath, newFileContent);
}

function CollectDependency(filePath: string, dependencyFileList: string[], collectedFileList: string[], depth: number = 0) {
    if (depth < 1) {
        dependencyFileList = [filePath];
    }
    let importFilePathList = allCodeFileImportFileListDict[filePath];
    if (importFilePathList) {
        for (let i = 0; i < importFilePathList.length; i++) {
            let importFilePath = importFilePathList[i];

            if (collectedFileList.indexOf(importFilePath) == -1) {
                collectedFileList.push(importFilePath);
            }
            if (dependencyFileList.indexOf(importFilePath) == -1) {
                dependencyFileList.push(importFilePath);
            } else {
                continue;
            }
            CollectDependency(importFilePath, dependencyFileList, collectedFileList, ++depth);
        }
    }
    return collectedFileList;
}

function BuildDependencyGraph(filePathList: string[]) {
    for (let i = 0; i < filePathList.length; i++) {
        let filePath = filePathList[i];
        let collectedFileList = CollectDependency(filePath, [], [filePath]);
        let filteredFileList = FilterCollectedFileList(filePath, collectedFileList);
        allCollectedFileImportFileListDict[filePath] = filteredFileList;
    }
}

function FilterCollectedFileList(selfFilePath: string, collectedFileList: string[]) {
    let filteredFileList: string[] = [];
    for (let i = 0; i < collectedFileList.length; i++) {
        let filePath = collectedFileList[i];
        if (selfFilePath == filePath) {
            continue;
        }
        if (totalFullPathList.indexOf(filePath) == -1) {
            continue;
        }
        filteredFileList.push(filePath);
    }
    return filteredFileList;
}

function GetCircularReferenceData(filePath: string) {
    let circularReferenceList: string[] = [];
    let dependencyFileList = allCollectedFileImportFileListDict[filePath];
    for (let j = 0; j < dependencyFileList.length; j++) {
        let dependencyFilePath = dependencyFileList[j];
        let dependencyDependencyFileList = allCollectedFileImportFileListDict[dependencyFilePath];
        if (dependencyDependencyFileList.indexOf(filePath) != -1) {
            if (circularReferenceList.indexOf(dependencyFilePath) == -1) {
                circularReferenceList.push(dependencyFilePath);
            }
        }
    }
    return circularReferenceList;
}

function GetSortedCollectedFilePathList(rootPath: string) {
    let totalPathList: string[] = [];
    let tempFullPathList: string[] = [];
    let tempPathList: string[] = [];
    for (let i = 0; i < totalFullPathList.length; i++) {
        let filePath = totalFullPathList[i];
        let circularReferenceList = GetCircularReferenceData(filePath);
        let isCircularReference = false;
        if (circularReferenceList.length > 0) {
            isCircularReference = true;
        }
        if (!CheckNeedPreLoad(filePath)) {
            tempFullPathList.push(filePath);
        } else if (isCircularReference) {
            console.log("Circular reference: " + filePath);
        } else {
            tempPathList.push(filePath);
        }
    }
    // console.log(tempPathList.length, tempFullPathList.length);
    while (tempPathList.length != 0) {
        let tempIndex = -1;
        for (let i = 0; i < tempPathList.length; i++) {
            let filePath = tempPathList[i];
            if (tempFullPathList.indexOf(filePath) != -1) {
                tempIndex = i;
                break;
            }
            let dependencyFileList = allCollectedFileImportFileListDict[filePath];
            let isAllCollected = true;
            for (let j = 0; j < dependencyFileList.length; j++) {
                let dependencyFilePath = dependencyFileList[j];
                if (!CheckNeedPreLoad(dependencyFilePath)) {
                    continue;
                }
                if (tempFullPathList.indexOf(dependencyFilePath) == -1) {
                    isAllCollected = false;
                    break;
                }
                if (allModuleFileList.indexOf(dependencyFilePath) != -1) {
                    let expandFileList = originModule2ExpandFileListDict[dependencyFilePath];
                    for (let k = 0; k < expandFileList.length; k++) {
                        let expandFilePath = expandFileList[k];
                        if (filePath == expandFilePath) {
                            continue;
                        }
                        if (!CheckNeedPreLoad(expandFilePath)) {
                            continue;
                        }
                        if (tempFullPathList.indexOf(expandFilePath) == -1) {
                            isAllCollected = false;
                            break;
                        }
                    }
                }
                if (!isAllCollected) {
                    break;
                }
            }
            if (isAllCollected) {
                tempIndex = i;
                break;
            }
        }
        if (tempIndex != -1) {
            let validPathList = tempPathList.splice(tempIndex, 1);
            for (let i = 0; i < validPathList.length; i++) {
                let validPath = validPathList[i];
                if (tempFullPathList.indexOf(validPath) == -1) {
                    tempFullPathList.push(validPath);
                }
            }
            // console.log(tempPathList.length, tempFullPathList.length);
        }
    }
    tempFullPathList.forEach((tempFilePath) => {
        if (!CheckNeedPreLoad(tempFilePath)) {
            return;
        }
        const relatFile = tempFilePath.replace(rootPath, "");
        if (totalPathList.indexOf(relatFile) == -1) {
            totalPathList.push(relatFile);
        }
    });
    return totalPathList;
}

function CheckNeedPreLoad(filePath: string) {
    let needPreLoad = true;
    if (filePath.startsWith(decoratorFolderPath) || filePath.startsWith(coreFolderPath) || filePath.startsWith(modelFolderPath)) {
        needPreLoad = false;
        return;
    }
    return needPreLoad;
}

const srcFolder = "Codes/scripts";
const needFilterFolderOrFileList = ["Client", "CodeLoader.ts", "CodePreLoader.ts"];
const rootPath = path.join(__dirname, "../../../");
const realPath = path.join(rootPath, srcFolder);

const cacheFilePath = "Support/Server/Loader/CodePreLoader.ts";
const cacheFileFolder = path.join(rootPath, srcFolder);
const cacheFileFullPath = path.join(cacheFileFolder, cacheFilePath);

Build(needFilterFolderOrFileList, realPath);

const [pathList, fullPathList] = GetSortedFilePathList(realPath);

const [otherFilePathList, otherFullPathList] = BuildOthers(fullPathList);

const totalFullPathList = fullPathList.concat(otherFullPathList);

BuildDependencyGraph(totalFullPathList);

const decoratorFolderPath = "E:\\Code\\Study\\ctdemo\\Codes\\scripts\\Decorator";
const coreFolderPath = "E:\\Code\\Study\\ctdemo\\Codes\\scripts\\Core";
const modelFolderPath = "E:\\Code\\Study\\ctdemo\\Codes\\scripts\\Codes\\Model";

const totalPathList = GetSortedCollectedFilePathList(realPath);

// BuildPreLoader(cacheFileFullPath, totalPathList);
console.log(totalPathList);